import json
import os
import pathlib
import traceback
from typing import List, Union

import pytest

from apitest.engine.env import run_env
from apitest.engine.logging import logger


# load_json 加载调用此函数的文件同级的json文件。
# 如 a.py 通过 b.py 里的函数调用load_json方法， 则会加载a.py同级的a.{env}.json文件
# from os.path import dirname, abspath
def load_json(filename, env_str):
    # 参数校验和处理
    if filename is None or filename == '':
        raise Exception('必须指定文件名file')
    if filename.endswith('.json'):
        filename = filename.rstrip('.json')
    filename = '{}.{}.json'.format(filename, env_str)
    if os.path.exists(filename):
        target = filename
    else:
        # 获得调用此函数的文件的目录
        stacks = traceback.extract_stack()
        target = str()
        for stack in reversed(stacks):
            parent = pathlib.Path(stack.filename).parent
            file_target = parent.joinpath(filename).__str__().replace("testcase", "data")
            if os.path.exists(file_target):
                target = file_target
                break
        if not target:
            logger.debug('项目data目录下无目标文件，文件名：{}'.format(filename))
            return []
    with open(target) as data:
        try:
            resp = json.load(data)
            return resp
        except Exception as e:
            raise Exception('{}/n 数据文件中json格式错误,请检查文件:{}'.format(e, filename))


# gen_data 生成case的参数列表
def gen_data(**kwargs):
    env_str = kwargs.get('env', 'boe')
    data = kwargs.get('data', None)
    filename = kwargs.get('file', None)
    # 优先指定data的
    if data is None and filename is None:
        raise Exception('file和data必须指定一个')
    raw = []
    if filename is not None:
        raw = load_json(filename, env_str)
    if data is not None:
        raw = data
    # 从数据中切割出title
    # 处理文件情况
    run = []
    if not raw:
        item = pytest.param(None, None, marks=pytest.mark.skip("{}环境数据无数据文件跳过case!".format(env_str)))
        run.append(item)
        return run
    for item in raw:
        if isinstance(item, str):
            raise Exception('数据文件中的json数据必须是个list')
        title = item.get('title', 'None')
        try:
            del item['title']
        except KeyError:
            pass
        if item.get('skip'):  # 如果testdata 的 json 文件里设置了 skip = true 的话，这条case会跳过。
            item = pytest.param(title, item, marks=pytest.mark.skip("用例数据驱动文件设置了skip"))
            run.append(item)
        else:
            run.append((title, item))
        logger.info(f"rst: {run}")
    return run


# gen_data 生成case的参数列表
def gen_parametrize(**kwargs):
    env_str = kwargs.get('env', 'boe')
    data = kwargs.get('data', None)
    filename = kwargs.get('file', None)
    # 优先指定data的
    if data is None and filename is None:
        raise Exception('file和data必须指定一个')
    raw = []
    if filename is not None:
        raw = load_json(filename, env_str)
    if data is not None:
        raw = data
    return raw


# runtime 装饰器，聚合了多个装饰器。用于根据不同环境，注入不同的函数参数。
# 使用：@runtime(Env.BOE, Env.Online)
# 环境标签无论设置几个，一次执行都只会使用一个
# 不同环境(env包内根据环境变量IDC判定)下的执行策略不同：
#   online：只执行Env.Online的装饰器
#   boe：只执行Env.BOE的装饰器
def runtime(*args, **kwargs):
    def wrapper(f):
        if run_env.region in args:
            f = getattr(pytest.mark, run_env.region.value)(f)
            if kwargs.get("file") or kwargs.get("data"):
                f = pytest.mark.parametrize('title,data', gen_data(env=run_env.region.value, **kwargs), indirect=True)(
                    f
                )
                return f
        else:
            f = pytest.mark.skip(
                reason="用例%s设置的运行环境为：%s，不包含当前运行环境：%s，跳过！" % (
                    f.__name__, args, run_env.region.value)
            )(
                f
            )

        if len(args) <= 0:
            raise Exception("必须设置环境标签 例如：@web_admin.runtime(Env.BOE, Env.Online) args:{}".format(args))
        return f

    return wrapper


# 使用：@runtime_env(Env.BOE, Env.Online)
# 用于表示用例可执行的环境区域
def runtime_env(*args):
    def wrapper(f):
        if run_env.region in args:
            f = getattr(pytest.mark, run_env.region.value)(f)
        else:
            f = pytest.mark.skip(reason="用例%s设置的运行环境为：%s，不包含当前运行环境：%s，跳过！" % (
                f.__name__, args, run_env.region.value)
                                 )(f)

        if len(args) <= 0:
            raise Exception("必须设置环境标签 例如：@web_admin.runtime(Env.BOE, Env.Online) args:{}".format(args))
        return f

    return wrapper


# 单独JSON文件形式数据驱动，可添加多个@runtime_parametrize(key, file=)使用
def runtime_parametrize(key: Union[str, List[str]] = "data", indirect=False, **kwargs):
    if isinstance(key, list):
        key = ",".join(key)
    if indirect is False and key == "data":
        indirect = True

    def wrapper(f):
        if kwargs.get("file") or kwargs.get("data"):
            data = gen_parametrize(env=run_env.region.value, **kwargs)
            if not data:
                f = pytest.mark.skip(reason="{}环境数据无数据文件跳过case!".format(run_env.region.value))(f)
                return f

            f = pytest.mark.parametrize(key, data, indirect=indirect)(f)
        return f

    return wrapper
